package com.wei.service.wrapper;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
import org.springframework.cache.support.SimpleValueWrapper;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.GenericToStringSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.io.*;
import java.util.Collections;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;

public class RedisCacheWrapper implements Cache {

    private static final Logger logger = LoggerFactory.getLogger(RedisCacheWrapper.class);
    private static final Long SUCCESS = 1L;

    private RedisTemplate<String, Object> redisTemplate;
    private String name;

    @Autowired
    private GenericJackson2JsonRedisSerializer serializer;
    @Autowired
    private StringRedisSerializer stringRedisSerializer;
    @Autowired
    private GenericToStringSerializer genericToStringSerializer;

    public RedisTemplate<String, Object> getRedisTemplate() {
        return redisTemplate;
    }
    public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) {

        this.redisTemplate = redisTemplate;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String getName() {
        return this.name;
    }

    @Override
    public Object getNativeCache() {
        return this.redisTemplate;
    }

    @Override
    public ValueWrapper get(Object key) {
        logger.info("get key:" + key);
        final String keyf = key.toString();
        Object object = getObject(keyf);
        return (object != null ? new SimpleValueWrapper(object) : null);
    }

    public Object getObject(String keyf) {
        Object object = null;
        object = redisTemplate.execute((RedisCallback<Object>) connection -> {
            byte[] key1 = keyf.getBytes();
            byte[] value = connection.get(key1);
            if (value == null) {
                return null;
            }
            return serializer.deserialize(value);//toObject(value);
        });
        return object;
    }

    @Override
    public <T> T get(Object key, Class<T> aClass) {
        logger.info("get key:" + key);
        final String keyf = key.toString();
        Object object = getObject(keyf);
        ObjectMapper objectMapper = new ObjectMapper();
        return objectMapper.convertValue(object, aClass);
    }

    @Override
    public <T> T get(Object key, Callable<T> callable) {
        return null;
    }

    /**
     * 如果该值没有设置过期时间，就返回-1;
     * 如果没有该值，就返回-2;
     * @param key
     * @return
     */
    public long getExpireTime(Object key){
        final String keyf = key.toString();

       Object result = redisTemplate.execute((RedisCallback<Object>) connection -> {
           byte[] key1 = keyf.getBytes();
           return connection.pTtl(key1,TimeUnit.SECONDS);
       });
       if(result==null){
           return 0L;
       }
       return (long)result;
    }

    @Override
    public void put(Object key, Object value) {
        final String keyf = key.toString();
        final Object valuef = value;
        final long liveTime = 86400;
        redisTemplate.execute((RedisCallback<Long>) connection -> {
            byte[] keyb = keyf.getBytes();
            byte[] valueb =  serializer.serialize(value);//toByteArray(valuef);
            connection.set(keyb, valueb);
            if (liveTime > 0) {
                connection.expire(keyb, liveTime);
            }
            return SUCCESS;
        });
    }

    public void put(Object key, Object value,long liveTime) {
        final String keyf = key.toString();
        final Object valuef = value;
        redisTemplate.execute((RedisCallback<Long>) connection -> {
            byte[] keyb = keyf.getBytes();
            byte[] valueb =  serializer.serialize(value);//toByteArray(valuef);
            connection.set(keyb, valueb);
            if (liveTime > 0) {
                connection.expire(keyb, liveTime);
            }
            return SUCCESS;
        });
    }

    public boolean hasKey(Object key){
        final String keyf = key.toString();
        byte[] keyb = keyf.getBytes();
        return redisTemplate.execute(connection -> connection.exists(keyb), true);
    }

    public void delete(Object key){
        final String keyf = key.toString();
        byte[] keyb = keyf.getBytes();
        redisTemplate.execute((RedisCallback<Object>) connection -> connection.del(keyb));
    }

    @Override
    public void evict(Object key) {
        logger.info("delete key:" + key);
        final String keyf = key.toString();
        redisTemplate.execute(new RedisCallback<Long>() {
            public Long doInRedis(RedisConnection connection)
                    throws DataAccessException {
                return connection.del(keyf.getBytes());
            }
        });
    }

    @Override
    public void clear() {
        logger.info("delete all key");
        redisTemplate.execute(new RedisCallback<String>() {
            public String doInRedis(RedisConnection connection)
                    throws DataAccessException {
                connection.flushDb();
                return "ok";
            }
        });
    }

//    private Object toObject(byte[] bytes) {
//        Object obj = null;
//        try {
//            ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
//            ObjectInputStream ois = new ObjectInputStream(bis);
//            obj = ois.readObject();
//            ois.close();
//            bis.close();
//        } catch (IOException ex) {
//            ex.printStackTrace();
//        } catch (ClassNotFoundException ex) {
//            ex.printStackTrace();
//        }
//        return obj;
//    }

//    private byte[] toByteArray(Object obj) {
//        byte[] bytes = null;
//        ByteArrayOutputStream bos = new ByteArrayOutputStream();
//        try {
//            ObjectOutputStream oos = new ObjectOutputStream(bos);
//            oos.writeObject(obj);
//            oos.flush();
//            bytes = bos.toByteArray();
//            oos.close();
//            bos.close();
//        } catch (IOException ex) {
//            ex.printStackTrace();
//        }
//        return bytes;
//    }

    /**
     * 加锁
     * 这种方式加锁会有一定的问题
     * 1. 在redis的master节点上拿到了锁
     * 2. 但是这个key没有同步到slave节点
     * 3. master节点故障 发生故障转移 slave节点升级成master节点
     * 4. 锁丢失
     * 正因为可能出现这种情况 redis作者提出了一种更高级的分布式锁实现方案 Redlock
     * @param lockKey key
     * @param value 通过赋值value就知道是哪个请求加的锁
     * @param expireTime 过期时间 s 秒
     * @return
     */
    public boolean tryLock(String lockKey, String value, int expireTime) {
        // 1.实现一
        String script = "if redis.call('setnx', KEYS[1], ARGV[1]) == 1 then redis.call('expire', KEYS[1], ARGV[2]) return 1 else return 0 end";
        RedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
        Object result = redisTemplate.execute(redisScript, genericToStringSerializer,genericToStringSerializer,
                Collections.singletonList(lockKey),
                value, expireTime);
        if (SUCCESS.equals(result)) {
            return true;
        }
        return false;
        // 2.实现二
       // return redisTemplate.opsForValue().setIfAbsent(lockKey, value, expireTime, TimeUnit.SECONDS);
    }

    /**
     * 一直等待加锁
     * @param lockKey key
     * @param value 通过赋值value就知道是哪个请求加的锁
     * @param expireTime 锁的过期时间
     * @return
     */
    public  boolean waitLock(String lockKey, String value, int expireTime){
        boolean loop = true;
        int waitTime = expireTime;
        while (loop){
            loop=!tryLock(lockKey,value,expireTime);
            if(waitTime<=0){
                return false;
            }
            if(loop) {
                try {
                    Thread.sleep(1000);
                    waitTime -= 1;
                } catch (InterruptedException ex) {
                    logger.error(ex.getMessage(),ex);
                    return false;
                }
            }
        }
        return !loop;
    }

    /**
     * 解锁
     * @param key key
     * @param value 通过value解锁
     * @return
     */
    public boolean unLock(String key, String value) {
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        RedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
        Object result = redisTemplate.execute(redisScript,genericToStringSerializer,genericToStringSerializer, Collections.singletonList(key), value);
        if (SUCCESS.equals(result)) {
            return true;
        }
        return false;
    }

}
